#!/usr/bin/env python
'''
Create a patch that shows when a supposedly not-covered function is called

This script makes it easy to find false positives from ccoverage and also
instruments source files to show when a new test or command calls a previously
untested function.

'''

from os import path
import difflib
import pickle
import ccoverage

# String to 'instrument' code, % ('/path/filename.c', 'function_name')
SUBST = '{ printf("%s %s COVERED\\n"); '

# These are files that the stupid patching code managed to break
BAD = set(['xmlparser.c', 'ceval.c', 'ffi.c', 'xmltok.c', 'xmlrole.c',
           'rotatingtree.c', 'xmlparse.c', 'statement.c'
           ])

def oktopatch(name):
    return path.basename(name) not in BAD

def decodopen(fname):
    ''' Avoid issues with non-ascii characters in source files.
    '''
    return [line.decode('utf-8', 'ignore') for line in open(fname).readlines()]

def process(pathfile, functions, original, coverage):
    ''' Crude 'instrumenting' of functions flagged as not-covered
    '''
    source = original[:]
    for function in functions:
        function = u':' + function[0] + u'('
        print function
        found = False
        for lnum, line in enumerate(coverage):
            if found and u'{' in line:
                snum = int(coverage[lnum][10:15]) - 1
                where = (pathfile, function[1:-1])
                source[snum] = source[snum].replace('{', SUBST % where)
                break
            if function in line:
                try:
                    if source[int(coverage[lnum][10:15]) - 1] in line:
                        found = 1
                except IndexError:
                    print(pathfile)
                    return False
    return source

def coveragediff(covdata, fname='coverage.diff'):
    outfile = open(fname, 'w')
    for pathfile, functions in covdata.iteritems():
        gcovname = path.basename(pathfile) + '.gcov'
        if path.exists(pathfile) and oktopatch(pathfile):
            coverage = decodopen(gcovname)
            original = decodopen(pathfile)
            patched = process(pathfile, functions, original, coverage)
            diff = difflib.unified_diff(original, patched,
                                        fromfile=pathfile, tofile=pathfile)
            if diff:
                # Mimick SVN diff
                header = 'Index: ' + pathfile + '\n' + '=' * 67 + '\n'
                outfile.write(header)
                outfile.writelines(diff)
        else:
            print('Could not find file: ' + gcovname)


def main():
    pickled = 'cov.pickle'
    if path.exists(pickled):
        print('Loading from: ' + pickled)
        pic = open(pickled)
        gcov_output = pickle.load(pic)
    else:
        covdirs = ccoverage.findcov()
        gcov_output = ccoverage.rungcov(covdirs)
    coveragediff(gcov_output)


if __name__ == '__main__':
    main()